package dpss.server;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.rmi.*;
import java.rmi.server.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import dpss.commons.CustomLogger;
import dpss.commons.Player;

/**
 * The implementation of the DpssServer interface
 * 
 * @author musab.mirza
 * 
 */
public class DpssApiImpl extends UnicastRemoteObject implements
		dpss.api.DpssApi {
	
	private static CustomLogger logger = CustomLogger.getInstance();

	private static final long serialVersionUID = 1L;

	/**
	 * Reference to the Server's account db
	 */
	private AccountDatabase accountDatabase = null;

	/**
	 * Constructor to start the accounts databases
	 * 
	 * @throws RemoteException
	 */
	protected DpssApiImpl(AccountDatabase accountDatabase)
			throws RemoteException {
		super();
		this.accountDatabase = accountDatabase;
	}

	@Override
	public synchronized Player createPlayerAccount(String firstName,
			String lastName, Integer age, String username, String password,
			String ipAddress) throws RemoteException {
		
		logger.log("Create player request from " + ipAddress + ":" + username);
		
		username = username.trim();
		if (username.equalsIgnoreCase("admin")){
			throw new RemoteException("The account 'admin' is reserved and cannot be registered");
		}

		Player p = null;

		// Validation on player account values
		try {
			p = new Player(username, password, age, ipAddress, firstName,
					lastName, true);
		} catch (Exception e) {
			throw new RemoteException(e.getMessage());
		}

		// Account addition validation
		try {
			accountDatabase.addAccount(p);
		} catch (Exception e) {
			throw new RemoteException(e.getMessage());
		}

		logger.log("Account created: " + username);
		
		return p;
	}

	@Override
	public synchronized Player playerSignIn(String username, String password,
			String ipAddress) throws RemoteException {
		
		logger.log("Player sign in request from " + ipAddress + ":" + username);

		Player p = null;
		try {
			p = accountDatabase.getAccount(username);
		} catch (Exception e) {
			throw new RemoteException(e.getMessage());
		}

		if (!(p.getPassword().trim().equalsIgnoreCase(password))) {
			throw new RemoteException("The password is incorrect");
		}

		if (p.isOnline()) {
			throw new RemoteException(
					"You are already signed in from another IP address");
		}

		p.setOnline(true);
		
		logger.log("Player signed in: " + username);
		
		return p;
	}

	@Override
	public synchronized void playerSignOut(String username, String ipAddress)
			throws RemoteException {
		
		logger.log("Player signout request from " + ipAddress + ":" + username);
		try {
			accountDatabase.getAccount(username).setOnline(false);
			;
		} catch (Exception e) {
			throw new RemoteException(e.getMessage());
		}
		
		logger.log("Player signed out: " + username);
	}

	@Override
	public synchronized String getPlayersStatus(String adminUsername,
			String adminPassword, String ipAddress) throws RemoteException {
		
		logger.log("Get player status request from " + adminUsername + ":" + ipAddress);

		if (!adminUsername.equalsIgnoreCase("admin")) {
			throw new RemoteException("You are not an admin");
		}

		try {
			if (!accountDatabase.getAccount("admin").getPassword()
					.equalsIgnoreCase(adminPassword.trim())) {
				throw new RemoteException("Invalid password");
			}
		} catch (Exception e) {
			throw new RemoteException("Account does not exist: admin");
		}

		try {
			logger.log("Returning player statuss request to " + ipAddress + ":" + adminUsername );
			return fetchPlayerStatusFromOtherServers();
		} catch (Exception e) {
			throw new RemoteException(e.getMessage());
		}

	}

	/**
	 * Pings all other servers through UDP and gets their player statuses
	 * 
	 * @return
	 * @throws Exception
	 */
	private String fetchPlayerStatusFromOtherServers() throws Exception {
		String playersStatus = Server.getServerProperties().getProperty(
				"server.name")
				+ ": " + accountDatabase.getPlayersInfo();
		

		// Loop through all repos and append their player info
		HashSet<HashMap<String, String>> allservers;
		DatagramSocket socket = null;

		try {
			allservers = Server.getRepo().getAllServers();
		} catch (ParserConfigurationException | SAXException | IOException e) {
			throw new Exception(
					"Could not connect to the repo OR could not initialize socket");
		}

		socket = new DatagramSocket();
		socket.setSoTimeout(1000);

		Iterator<HashMap<String, String>> serverItemIter = allservers.iterator();
		while (serverItemIter.hasNext()) {
			
			
			HashMap<String, String> serverItem = serverItemIter.next();

			// ping all other servers than the current one
			if (!(serverItem.get("id").equalsIgnoreCase(Server
					.getServerProperties().getProperty("server.id")))) {

				byte[] sendData = new byte[1024];
				byte[] receiveData = new byte[1024];
				sendData = "getplayersinfo".getBytes();
				DatagramPacket sendPacket = new DatagramPacket(sendData,
						sendData.length, InetAddress.getLocalHost(),
						Integer.parseInt((String) serverItem.get("udpport")));
				socket.send(sendPacket);
				DatagramPacket receivePacket = new DatagramPacket(receiveData,
						receiveData.length);
				try {
					socket.receive(receivePacket);
					String receiveStr = new String(receivePacket.getData()).trim();
					playersStatus = playersStatus + " | "
							+ serverItem.get("name") + ": "
							+ receiveStr;
				} catch (SocketTimeoutException e) {
					playersStatus = playersStatus + " | "
							+ serverItem.get("name") + " (timeout) ";
				}

			}
		}

		socket.close();
		return playersStatus;
	}
}
